﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Data;
using System.Reflection;
using IronPython.Hosting;
using IronPython.Runtime;
using IronPython.Runtime.Exceptions;
using Microsoft.Scripting;
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Hosting;
using Microsoft.Scripting.Hosting.Providers;
using Microsoft.Win32;

namespace MyClever.Lib
{
    class PythonPluginBuilder : PluginBuilder
    {
        private string fileContent;
        private const string docSeparator = "\"\"\"";

        public PythonPluginBuilder(PluginPath pluginPath, string pluginClass, ProgrammingLanguage programmingLanguage)
            : base(pluginPath, pluginClass, programmingLanguage)
        {
            this.fileContent = File.ReadAllText(this.path.FullPath);

            this.properties.Add(PluginProperty.Language, ProgrammingLanguage.Python);
        }

        public override void ExtractProperties()
        {
            Regex regularExpression = new Regex("__([a-zA-D]+)__ = \"([^\"]+)", RegexOptions.Multiline);
            Match regularMatch = regularExpression.Match(this.GetRelevantPropertyString());
            while (regularMatch.Success)
            {
                string propertyName = regularMatch.Groups[1].Captures[0].Value;
                string propertyValue = regularMatch.Groups[2].Captures[0].Value;

                if (String.Equals(propertyName, "maintainer", StringComparison.CurrentCultureIgnoreCase) || String.Equals(propertyName, "version", StringComparison.CurrentCultureIgnoreCase))
                {
                    regularMatch = regularMatch.NextMatch();
                    continue;
                }
                this.properties.Add((PluginProperty)Enum.Parse(typeof(PluginProperty), propertyName, true), propertyValue);

                regularMatch = regularMatch.NextMatch();
            }
        }

        public override void ExtractPackages()
        {
            Regex regularExpression = new Regex("(<?)-->?\r\n(.+\r\n)+<?-->?", RegexOptions.Multiline);
            Match regularMatch = regularExpression.Match(this.GetDocumentation());
            while (regularMatch.Success)
            {
                // get package-direction
                Direction packageDirection = Direction.Input;
                if (regularMatch.Groups[1].Captures[0].Value == "<")
                {
                    packageDirection = Direction.Output;
                }

                // get package itself
                CaptureCollection general = regularMatch.Groups[2].Captures;
                string[] packageData = general[0].Value.Split('=');
                PackageDefinition packageDefinition = new PackageDefinition(packageData[0].Trim(), packageDirection, packageData[1].Trim(), general.Count == 1);

                // index = 0 contains the packageName and packageDescription
                for (int i = 1; i < general.Count; i++)
                {
                    string[] columnDefinitionOn = general[i].Value.Split('=');
                    string columnName = columnDefinitionOn[0].Trim();
                    string columnAttributeDefinition = columnDefinitionOn[1];

                    // run through every column-attribue ...
                    Dictionary<string, string> columnAttributes = this.GetColumnAttributes(columnAttributeDefinition);

                    // create a new column
                    PackageColumnDefinition column = new PackageColumnDefinition(columnName, Type.GetType(columnAttributes["type"]));

                    // save the description of the column
                    column.extendedProperties.Add(MissionPluginPackageColumn.ExtendedPropertiesKey.Description, columnAttributes["description"]);

                    // is there any validation-rule? (optional!)
                    if (true == columnAttributes.ContainsKey("validation"))
                    {
                        column.extendedProperties.Add(MissionPluginPackageColumn.ExtendedPropertiesKey.Validation, columnAttributes["validation"]);
                    }

                    // is there any default-value? (optional!)
                    if (true == columnAttributes.ContainsKey("defaultvalue"))
                    {
                        column.defaultValue = columnAttributes["defaultvalue"];
                    }
                    packageDefinition.columnDefinitions.Add(column);
                }
                this.packages.Add(packageDefinition);

                regularMatch = regularMatch.NextMatch();
            }
        }

        public override object GetInstance()
        {
            string python27Path = (string)Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\2.7\\InstallPath", "", "Return this default if NoSuchName does not exist.");
            string ironPython27Path = (string)Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\2.7\\InstallPath", "", "Return this default if NoSuchName does not exist.");
            
            ScriptEngine engine = Python.CreateEngine();
            ScriptScope scope = engine.CreateScope();

            //add Assemblies here!!
            Assembly assembly = Assembly.Load("MyCleverLib");
            engine.Runtime.LoadAssembly(assembly);

            List<string> pythonLibraries = new List<string>();
            pythonLibraries.Add("C:\\Python27\\Lib");
            pythonLibraries.Add("C:\\Python27\\Lib\\site-packages");
            pythonLibraries.Add("C:\\Python27\\DLLs");
            pythonLibraries.Add("C:\\Program Files\\IronPython 2.7\\Lib");
            pythonLibraries.Add("C:\\Program Files\\IronPython 2.7\\Lib\\site-packages\\twitter-1.9.0-py2.7.egg");
            pythonLibraries.Add("C:\\Program Files\\IronPython 2.7\\DLLs\\");
            engine.SetSearchPaths(pythonLibraries);

            ScriptSource script = engine.CreateScriptSourceFromString(this.fileContent, SourceCodeKind.Statements);
            CompiledCode code = script.Compile();

            try
            {
                code.Execute(scope);
            }
            catch (Exception e)
            {
                // is there any execute exception? so we ask for a human readable error message
                ExceptionOperations eo = engine.GetService<ExceptionOperations>();
                string error = eo.FormatException(e);
                throw new SystemException(error);
            }

            // create plugin instance        
            object plugin = scope.GetVariable(this.className);
            object pluginInstance = engine.Operations.Invoke(plugin);

            return pluginInstance;
        }

        private Dictionary<string, string> GetColumnAttributes(string columnDefinition)
        {
            Dictionary<string, string> columnAttributes = new Dictionary<string, string>();

            //delete all whitespace characters and qoute-types: " '
            string[] columnTypes = Regex.Replace(columnDefinition, "[{}\r\n\t\"']", "").Split(',');
            foreach (string columnTypeOn in columnTypes)
            {
                string[] type = columnTypeOn.Split(':');
                columnAttributes.Add(type[0].ToLower().Trim(), type[1].Trim());
            }

            return columnAttributes;
        }

        private string GetDocumentation()
        {
            int startDoc = this.fileContent.IndexOf(docSeparator, StringComparison.CurrentCultureIgnoreCase) + docSeparator.Length;
            int endDoc = fileContent.IndexOf(docSeparator, startDoc, StringComparison.CurrentCultureIgnoreCase);

            return this.fileContent.Substring(startDoc, endDoc - startDoc);
        }

        private string GetRelevantPropertyString()
        {
            // get index of the SECOND docString
            int startDoc = this.fileContent.IndexOf(docSeparator, fileContent.IndexOf(docSeparator) + docSeparator.Length);
            int endDoc = this.fileContent.IndexOf("class", startDoc, StringComparison.CurrentCultureIgnoreCase);

            return this.fileContent.Substring(startDoc, endDoc - startDoc);
        }
    }
}
